package com.franklin.java.action.sql.entity.ui;

import com.flychord.jdk.util.tuple.Tuple3;
import com.franklin.java.action.sql.entity.EntityPathState;
import com.franklin.java.action.sql.entity.GenerateEntityClassAction;
import com.franklin.java.api.exception.PsiException;
import com.franklin.java.api.psi.comment.PsiCommentResolverFactory;
import com.franklin.java.api.ui.jpanel.AbstractComponent;
import com.franklin.java.api.ui.jpanel.MyJScrollPane;
import com.franklin.java.api.ui.jpanel.button.MyButton;
import com.franklin.java.api.ui.jpanel.checkbox.MultipleCheckBox;
import com.franklin.java.configuration.sql.completion.CompletionSettingState;
import com.franklin.java.configuration.sql.type.TypeMappingState;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.entity.sql.MappingType;
import com.franklin.java.icon.EntityIcons;
import com.franklin.java.icon.MybatisIcons;
import com.franklin.java.utils.*;
import com.franklin.plugins.common.collection.PrettyHashSet;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.StringHelper;
import com.google.common.collect.Lists;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.ClassFilter;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.InputValidator;
import com.intellij.openapi.ui.VerticalSeparatorComponent;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.file.PsiDirectoryImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.awt.RelativePoint;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.plaf.basic.BasicSeparatorUI;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;

/**
 * @author Franklin
 * @since 2021/09/12 18:50
 */
public class GenerateEntityClassForm extends AbstractComponent {
    private JPanel mainPanel;
    private JTree resultMapTree;
    private JLabel ResultMapStructureTitle;
    private JLabel EntityStructureTitle;
    private JTabbedPane entitySettingTab;
    private MyButton generateBtn;
    private JLabel entitySettingTitle;
    private JPanel tabPanel;
    private MyButton cancelBtn;
    private JScrollPane treeScrollPane;
    private JTabbedPane resultMapTab;
    private JTree normalTree;
    private Editor entityEditor;
    private JLabel commonEntitySettingTitle;
    private JPanel entitySettingPanel;
    private JScrollPane entityScrollPane;
    private JPanel entityPanel;
    private JPanel commonEntitySettingPanel;
    private JCheckBox serializableCheckBox;
    private JPanel fileChoosePanel;
    private MyButton openFileButton;
    private JPanel packagePanel;
    private JPanel pathPanel;
    private JTextField entityPackageTextField;
    private JTextField lastPathTextField;
    private JSplitPane splitPane;

    private PsiElement sqlActionElement;
    private List<ColumnInfo> columnInfoList;
    private GenerateEntityClassAction generateEntityClassAction;

    private MultipleCheckBox lombokCheckBox;
    private MultipleCheckBox normalPojoCheckBox;

    public final static String prefixTab = "\t";
    private final static String sourcePath = "src/main/java/";
    private final static String headerEl = "#{ideaFileHeader}";

    public GenerateEntityClassForm(List<ColumnInfo> columnInfoList, PsiElement sqlActionElement, GenerateEntityClassAction generateEntityClassAction) {
        this.columnInfoList = columnInfoList;
        this.sqlActionElement = sqlActionElement;
        this.generateEntityClassAction = generateEntityClassAction;

        init();
    }

    private void init() {
        initFileChoosePanel();
        initResultMapTree();
        initNormalTree();
        initResultMapTab();
        initEntitySettingTab();
        initCommonEntitySettings();
        initSerializableCheckBox();
        initGenerateBtn();

        initEntityEditor();
    }

    /**
     * 执行代码生成<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/15 18:06
     */
    private void initGenerateBtn() {
        this.generateBtn.addActionListener(e -> {
            try {
                checkIfGenerateAble();
                doCreateClass();
                doCreateResultMap();
                doChangeSelectTagResultMap();
                doChangeMapperMethodReturnType();
            } catch (PsiException psiException) {
                NotificationUtils.notifyError(
                        sqlActionElement.getProject(),
                        psiException.getMessage(),
                        psiException.getPsiElement(),
                        psiException.getTargetName()
                );
            }
            this.cancelBtn.doClick();
        });
    }

    private String getJavaFilePath(ResultMapNode resultMapNode) {
        String className = resultMapNode.getClassName();
        String path = this.lastPathTextField.getText();
        return path + "/" + className + ".java";
    }

    /**
     * 检查是否可以执行代码生成<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/17 16:07
     */
    private void checkIfGenerateAble() throws PsiException {
        //检查文件是否存在
        LocalFileSystem localFileSystem = LocalFileSystem.getInstance();
        String path = this.lastPathTextField.getText();
        ResultMapNode resultMapNode = getResultMapNode();
        String className = resultMapNode.getClassName();
        VirtualFile javaFile = localFileSystem.findFileByPath(path + "/" + className + ".java");
        if (Objects.nonNull(javaFile) && javaFile.exists()) {
            PsiFile psiFile = PsiManager.getInstance(sqlActionElement.getProject()).findFile(javaFile);
            throw new PsiException(psiFile, psiFile.getName(), getJavaFilePath(resultMapNode) + " is already exists");
        }

        //检查ResultMap是否存在
        XmlTag selectTag = (XmlTag) this.sqlActionElement;
        XmlTag mapperTag = selectTag.getParentTag();
        PsiFile xmlFile = selectTag.getContainingFile();
        String xmlPath = xmlFile.getVirtualFile().getPath();
        if (Objects.isNull(mapperTag)) {
            throw new PsiException(xmlFile, xmlFile.getName(), "mapper tag is not exists in this xml :" + xmlPath);
        }

        String resultMapId = resultMapNode.getProperty("id");
        XmlTag resultMapTag = XmlTagUtils.findChildTag(mapperTag, "resultMap", "id", resultMapId);
        if (Objects.nonNull(resultMapTag)) {
            throw new PsiException(resultMapTag, resultMapId, "resultMap : " + resultMapId + " is already exists in this xml : " + xmlPath);
        }
    }

    /**
     * 修改Mapper方法的返回值<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/17 15:54
     */
    private void doChangeMapperMethodReturnType() {
        PsiMethod mapperMethod = generateEntityClassAction.getMapperMethod();
        if (Objects.isNull(mapperMethod)) {
            doCreateMapperMethod();
            return;
        }
        Project project = mapperMethod.getProject();
        PsiType returnType = mapperMethod.getReturnType();
        String className = getResultMapNode().getClassName();
        String simpleName = StringHelper.getSimpleName(className);
        //没有则添加，有则修改
        if (Objects.nonNull(returnType)) {
            String simpleTypeName = returnType.getPresentableText();
            String genericType = PsiUtils.resolveGenericType(simpleTypeName);
            String replaceTargetName = simpleTypeName;
            if (DataCheckUtils.isNotEmpty(genericType)) {
                //修改原来的泛型
                replaceTargetName = genericType;
            }
            Set<String> importSet = new PrettyHashSet<>();
            importSet.add(className);
            String mapperMethodText = mapperMethod.getText();
            int offset = mapperMethodText.indexOf(replaceTargetName);
            if (offset < 0) {
                offset = mapperMethodText.indexOf(StringUtils.deleteWhitespace(replaceTargetName));
            }
            PsiElement returnTypeElement = mapperMethod.findElementAt(offset);
            if (Objects.nonNull(returnTypeElement)) {
                int start = returnTypeElement.getTextOffset();
                String finalReplaceTargetName = replaceTargetName;
                PsiFile mapperFile = mapperMethod.getContainingFile();
                DocumentUtils.writeSync(project, () -> {
                    PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
                    Optional.ofNullable(psiDocumentManager.getDocument(mapperFile))
                            .ifPresent(document -> {
                                document.replaceString(start, start + finalReplaceTargetName.length() - 1, simpleName);
                                psiDocumentManager.commitDocument(document);
                            });
                });

                //导包
                doImportClassToMapperClass(mapperFile, importSet);
            }
        } else {
            //没有则添加
            // TODO: 2021/9/18 没有则添加，需要弄吗？？？
        }
    }

    /**
     * 向Mapper接口导包<br>
     *
     * @param mapperFile:
     * @param importSet:
     * @return void
     * @author Franklin
     * @since 2021/9/18 10:57
     */
    private void doImportClassToMapperClass(PsiFile mapperFile, Set<String> importSet) {
        PsiJavaFile psiJavaFile = (PsiJavaFile) mapperFile;
        PsiImportList importList = psiJavaFile.getImportList();
        if (Objects.nonNull(importList)) {
            PsiImportStatement[] importStatements = importList.getImportStatements();
            for (PsiImportStatement importStatement : importStatements) {
                String qualifiedName = importStatement.getQualifiedName();
                if (importSet.contains(qualifiedName)) {
                    importSet.remove(qualifiedName);
                }
            }
        }
        if (DataCheckUtils.isNotEmpty(importSet)) {
            PsiPackageStatement packageStatement = psiJavaFile.getPackageStatement();
            int start = 0;
            if (Objects.nonNull(packageStatement)) {
                start = packageStatement.getTextOffset() + packageStatement.getTextLength();
            }
            String importString = importSet.stream()
                    .map(importTarget -> "import " + importTarget + ";\n")
                    .collect(Collectors.joining(""));
            Project project = mapperFile.getProject();
            int finalStart = start;
            DocumentUtils.writeSync(project, () -> {
                PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
                Optional.ofNullable(psiDocumentManager.getDocument(mapperFile))
                        .ifPresent(document -> {
                            document.insertString(finalStart, "\n\n" + importString);
                            psiDocumentManager.commitDocument(document);
                        });
            });
        }
    }

    /**
     * 没有Mapper方法则创建Mapper方法<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/18 10:12
     */
    private void doCreateMapperMethod() {
        PsiFile xmlFile = this.sqlActionElement.getContainingFile();
        String path = xmlFile.getVirtualFile().getPath();
        String xmlName = xmlFile.getName();
        String mapperName = xmlName.replaceAll(".xml", "");
        Project project = sqlActionElement.getProject();
        Module module = ModuleUtil.findModuleForPsiElement(sqlActionElement);
        PsiClass mapperClass = ModuleUtils.findClassBySimpleName(module, path, mapperName);
        if (Objects.isNull(mapperClass)) {
            return;
        }
        PsiFile mapperFile = mapperClass.getContainingFile();
        XmlTag selectTag = (XmlTag) this.sqlActionElement;
        String mapperMethodName = selectTag.getAttributeValue("id");
        if (DataCheckUtils.isEmpty(mapperMethodName)) {
            return;
        }

        String className = getResultMapNode().getClassName();
        String simpleName = StringHelper.getSimpleName(className);
        Set<String> importSet = new PrettyHashSet<>();
        importSet.add("java.util.List");
        importSet.add(className);
        StringBuilder mapperMethodBuilder = new StringBuilder();
        mapperMethodBuilder
                .append('\n')
                .append(prefixTab).append("List<").append(simpleName).append(">")
                .append(' ').append(mapperMethodName).append("();\n");

        PsiMethod[] psiMethods = mapperClass.getMethods();
        int start = 0;
        if (ArrayUtils.isNotEmpty(psiMethods)) {
            PsiMethod beforeMethod = psiMethods[psiMethods.length - 1];
            start = beforeMethod.getTextOffset() + beforeMethod.getTextLength();
        } else {
            start = mapperClass.getLastChild().getTextOffset() - 1;
        }

        int finalStart = start;
        DocumentUtils.writeSync(project, () -> {
            PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
            Optional.ofNullable(psiDocumentManager.getDocument(mapperFile))
                    .ifPresent(document -> {
                        document.insertString(finalStart, mapperMethodBuilder.toString());
                        psiDocumentManager.commitDocument(document);
                    });
        });

        //导包
        doImportClassToMapperClass(mapperFile, importSet);
    }

    /**
     * 修改Select标签的ResultMap<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/17 14:25
     */
    private void doChangeSelectTagResultMap() {
        Project project = sqlActionElement.getProject();
        XmlTag selectTag = (XmlTag) this.sqlActionElement;
        ResultMapNode resultMapNode = getResultMapNode();
        String resultMapId = resultMapNode.getProperty("id");
        DocumentUtils.writeSync(project, () -> {
            XmlAttribute resultType = selectTag.getAttribute("resultType");
            if (Objects.nonNull(resultType)) {
                int start = PsiUtils.findStartOffsetInPsiFile(resultType);
                PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
                Optional.ofNullable(psiDocumentManager.getDocument(resultType.getContainingFile()))
                        .ifPresent(document -> {
                            document.deleteString(start, start + resultType.getTextLength());
                            psiDocumentManager.commitDocument(document);
                        });
            }
            selectTag.setAttribute("resultMap", resultMapId);
        });
    }

    /**
     * 生成ResultMap<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/16 17:18
     */
    private void doCreateResultMap() {
        XmlTag selectTag = (XmlTag) this.sqlActionElement;
        XmlTag mapperTag = selectTag.getParentTag();
        if (Objects.isNull(mapperTag)) {
            return;
        }
        ResultMapNode resultMapNode = getResultMapNode();
        XmlTag resultMapTag = resultMapNode.parseXmlTag(mapperTag);

        String packagePath = entityPackageTextField.getText();
        String typeQualifiedName = packagePath + "." + resultMapNode.getClassName();
        resultMapTag.setAttribute("type", typeQualifiedName);

        DocumentUtils.writeSync(sqlActionElement.getProject(), () -> mapperTag.addSubTag(resultMapTag, true));
    }

    /**
     * 执行类生成操作<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/16 17:06
     */
    private void doCreateClass() {
        Project project = sqlActionElement.getProject();
        Document document = this.entityEditor.getDocument();
        String text = document.getText();
        JavaDirectoryService javaDirectoryService = JavaDirectoryService.getInstance();
        PsiManagerImpl psiManager = (PsiManagerImpl) PsiManager.getInstance(project);
        String path = this.lastPathTextField.getText();
        ResultMapNode resultMapNode = getResultMapNode();
        String className = resultMapNode.getClassName();
        String simpleName = StringHelper.getSimpleName(className);
        LocalFileSystem localFileSystem = LocalFileSystem.getInstance();
        VirtualFile virtualFile = localFileSystem.findFileByPath(path);

        PsiClass psiClass = javaDirectoryService.createClass(new PsiDirectoryImpl(psiManager, virtualFile), simpleName);

        String comment = PsiCommentResolverFactory.getInstance().resolveComment(psiClass);

        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
        Optional.ofNullable(psiDocumentManager.getDocument(psiClass.getContainingFile()))
                .ifPresent(classDocument -> {
                    String finalText = StringHelper.fillElExpression(text, headerEl, comment);
                    DocumentUtils.writeSync(project, () -> classDocument.setText(finalText));
                    psiDocumentManager.commitDocument(classDocument);
                });
    }

    private void initFileChoosePanel() {

        Project project = sqlActionElement.getProject();
        this.entityPackageTextField.setText(EntityPathState.getLastPackage(project));
        this.lastPathTextField.setText(EntityPathState.getLastPath(project));
        this.entityPackageTextField.setEditable(true);


        JTree jTree = getTree();
        TreePath selectionPath = jTree.getSelectionPath();
        this.entityPackageTextField.addActionListener(e -> {
            String newPackage = entityPackageTextField.getText();
            if (DataCheckUtils.isNotEmpty(newPackage)) {
                entityPackageTextField.setText(newPackage);
                String lastPath = lastPathTextField.getText();
                if (lastPath.contains(sourcePath)) {
                    lastPath = lastPath.substring(0, lastPath.indexOf(sourcePath)) + sourcePath + newPackage.replaceAll("\\.", "/");
                }
                lastPathTextField.setText(lastPath);
                EntityPathState.getInstance(project).lastPackage = newPackage;
                EntityPathState.getInstance(project).lastPath = lastPath;
                repaintTree(selectionPath, jTree);
            }
        });


        this.openFileButton.addActionListener(e -> {
            VirtualFile targetPath = FileChooser.chooseFile(
                    new FileChooserDescriptor(false, true, false, false, false, false),
                    project,
                    null
            );
            if (Objects.isNull(targetPath)) {
                return;
            }
            String newPath = targetPath.getPath();
            this.lastPathTextField.setText(newPath);

            String newPackage = Optional.ofNullable(project.getBasePath())
                    .filter(newPath::startsWith)
                    .map(basePath -> newPath)
                    .filter(path -> path.contains(sourcePath))
                    .map(path -> {
                        String packagePath = path.substring(path.indexOf(sourcePath) + sourcePath.length());
                        return packagePath.replaceAll("/", ".");
                    })
                    .orElse("");
            if (DataCheckUtils.isNotEmpty(newPackage)) {
                entityPackageTextField.setText(newPackage);
                EntityPathState.getInstance(project).lastPackage = newPackage;
                repaintTree(selectionPath, jTree);
            }


            EntityPathState.getInstance(project).lastPath = newPath;
        });
    }

    private void resetResultMapNodeType() {
        ResultMapNode resultMapNode = getResultMapNode();
        String className = StringHelper.getSimpleName(resultMapNode.getClassName());
        resultMapNode.setProperty("type", entityPackageTextField.getText() + "." + className);
    }

    private void initSerializableCheckBox() {
        serializableCheckBox.addActionListener(e -> refreshEntityEditor());
    }

    private void initCommonEntitySettings() {

    }

    private void initEntityEditor() {
        ResultMapNode resultMapNode = getResultMapNode();

        EditorFactory editorFactory = EditorFactory.getInstance();
        Document document = editorFactory.createDocument("");
        entityEditor = editorFactory.createEditor(document);
        EditorUtils.initEditor(entityEditor);

        Project project = sqlActionElement.getProject();
        boolean useCamel = CompletionSettingState.isUseCamel(project);
        Map<String, List<ColumnInfo>> columnNameColumnsMap = this.columnInfoList.stream()
                .collect(Collectors.groupingBy(columnInfo -> {
                    String columnName = columnInfo.getColumnName();
                    if (DataCheckUtils.isNotEmpty(columnInfo.getAlias())) {
                        columnName = columnInfo.getAlias();
                    }
                    if (useCamel) {
                        columnName = StringHelper.underlineToCamel(columnName);
                    }
                    return columnName;
                }));

        Map<String, String> tableNameTableCommentMap = new LinkedHashMap<>();
        this.columnInfoList
                .forEach(columnInfo -> tableNameTableCommentMap.put(columnInfo.getTableName(), columnInfo.getTableComment()));

        String className = resultMapNode.getProperty("type");
        if (!className.contains(".")) {
            className = StringHelper.firstToUpperCase(className);
        }
        String simpleName = StringHelper.getSimpleName(className);
        List<ResultMapChildNode> childrenNodeList = resultMapNode.getChildrenNodeList();
        MappingType mappingType = TypeMappingState.getMappingType(project);
        StringBuilder memberBuilder = new StringBuilder();
        StringBuilder methodBuilder = new StringBuilder();
        StringBuilder constructorBuilder = new StringBuilder();
        StringBuilder annBuilder = new StringBuilder();
        Set<String> importSet = new PrettyHashSet<>();
        for (ResultMapChildNode resultMapChildNode : childrenNodeList) {
            Tuple3<String, String, String> javaField = resultMapChildNode.getJavaField(columnNameColumnsMap, tableNameTableCommentMap, importSet);
            memberBuilder
                    .append(javaField._1())
                    .append('\n');

            if (entitySettingTab.getSelectedIndex() == 1) {
                //使用普通getter / setter
                List<String> selectItems = normalPojoCheckBox.selectItems();
                methodBuilder.append(
                        resultMapNode.getJavaFieldMethods(
                                selectItems,
                                javaField._2(),
                                javaField._3(),
                                className
                        )
                );
            }

            String path = sqlActionElement.getContainingFile().getVirtualFile().getPath();
            String otherImport = resultMapChildNode.otherImport(path);
            if (DataCheckUtils.isNotEmpty(otherImport)) {
                importSet.add(otherImport);
            }

            String classMember = resultMapChildNode.classMember(className, importSet);
            if (DataCheckUtils.isNotEmpty(classMember)) {
                constructorBuilder.append(classMember);
            }
        }

        if (entitySettingTab.getSelectedIndex() == 0) {
            Set<String> lombokSet = new PrettyHashSet<>(lombokCheckBox.selectItems());
            if (lombokSet.contains("@Data")) {
                lombokSet.remove("@Getter");
                lombokSet.remove("@Setter");
            }
            lombokSet.forEach(lombokAnn -> {
                String lombokName = lombokAnn.replaceAll("@", "");
                importSet.add("lombok." + lombokName);
                annBuilder.append(lombokAnn).append('\n');
            });
        }

        if (methodBuilder.length() > 0) {
            memberBuilder.append('\n').append(methodBuilder);
        }

        StringBuilder fileBuilder = new StringBuilder();

        if (serializableCheckBox.isSelected()) {
            importSet.add("java.io.Serializable");
        }
        String importListStr = importSet.stream()
                .map(importTarget -> "import " + importTarget + ';')
                .collect(Collectors.joining("\n"));

        fileBuilder
                .append("package ").append(this.entityPackageTextField.getText()).append(";\n")
                .append('\n').append(importListStr).append('\n').append('\n')
                .append(headerEl).append('\n')
                .append(annBuilder)
                .append("public class ").append(simpleName);
        if (serializableCheckBox.isSelected()) {
            fileBuilder.append(" implements Serializable {\n\n");
            fileBuilder.append(prefixTab).append("private static final long serialVersionUID = 1L;\n\n");
        } else {
            fileBuilder.append(" {\n\n");
        }

        if (constructorBuilder.length() > 0) {
            fileBuilder.append(prefixTab).append(constructorBuilder).append('\n');
        }

        fileBuilder
                .append(memberBuilder)
                .append("\n}");


        this.entityPanel.removeAll();
        this.entityPanel.setLayout(SwingUtils.createLayoutManager(1, 1));
        JComponent editorComponent = entityEditor.getComponent();
        editorComponent.setEnabled(true);

        refreshEntityEditorUI(fileBuilder, className);


        this.addCell(this.entityPanel, editorComponent, 0, 0, new Dimension(600, 500));


    }

    private JTree getTree() {
        if (resultMapTab.getSelectedIndex() == 0) {
            return resultMapTree;
        }
        return normalTree;
    }

    private ResultMapNode getResultMapNode() {
        return (ResultMapNode) getTree().getModel().getRoot();
    }

    private void refreshEntityEditorUI(CharSequence content, String className) {
        ((EditorImpl) this.entityEditor).setViewer(true);
        Project project = sqlActionElement.getProject();
        DocumentUtils.writeSync(project, () -> entityEditor.getDocument().setText(content));
        ((EditorEx) entityEditor)
                .setHighlighter(EditorHighlighterFactory.getInstance()
                        .createEditorHighlighter(project, className + ".java")
                );
    }


    private void initResultMapTab() {
        resultMapTab.addChangeListener(e -> initEntityEditor());
    }

    /**
     * 实体设置<br>
     *
     * @param :
     * @return void
     * @author Franklin
     * @since 2021/9/13 11:17
     */
    private void initEntitySettingTab() {
        List<String> items = Lists.newArrayList(
                "@Data", "@Getter", "@Setter",
                "@Builder", "@NoArgsConstructor", "@AllArgsConstructor"
        );
        lombokCheckBox = new MultipleCheckBox(
                items,
                items
        );
        lombokCheckBox.addActionListener(e -> refreshEntityEditor());

        entitySettingTab.removeTabAt(0);
        entitySettingTab.addTab(
                "Lombok", EntityIcons.Lombok.lombokIcon, lombokCheckBox
        );

        List<String> normalItems = Lists.newArrayList(
                "toString", "getter", "setter", "equals&HashCode"
        );
        normalPojoCheckBox = new MultipleCheckBox(
                normalItems,
                normalItems
        );
        normalPojoCheckBox.addActionListener(e -> refreshEntityEditor());
        entitySettingTab.addTab("Normal Pojo", normalPojoCheckBox);

        entitySettingTab.addChangeListener(e -> refreshEntityEditor());
    }

    private void refreshEntityEditor() {
        int selectedIndex = this.resultMapTab.getSelectedIndex();
        int newSelectedIndex = (selectedIndex + 1) % 2;
        this.resultMapTab.setSelectedIndex(newSelectedIndex);
        this.resultMapTab.setSelectedIndex(selectedIndex);
    }

    private void initResultMapTree() {
        ResultMapTreeModel resultMapTreeModel = new ResultMapTreeModel(
                new ResultMapNode(
                        this.columnInfoList,
                        sqlActionElement,
                        this.entityPackageTextField.getText() + "." + "Type"
                )
        );
        resultMapTree.setModel(resultMapTreeModel);
        initTree(resultMapTree);
        resultMapTree.setVisible(true);

    }

    private void initNormalTree() {
        ResultMapTreeModel resultMapTreeModel = new ResultMapTreeModel(
                new ResultMapNode.NormalResultMapNode(
                        this.columnInfoList, sqlActionElement,
                        this.entityPackageTextField.getText() + "." + "Type"
                ));
        normalTree.setModel(resultMapTreeModel);
        initTree(normalTree);
        normalTree.setVisible(true);
    }

    private void initTree(JTree jTree) {
        addTreeListener(jTree);

        DefaultTreeCellRenderer cellRenderer = (DefaultTreeCellRenderer) jTree.getCellRenderer();
        cellRenderer.setLeafIcon(MybatisIcons.logo);
        cellRenderer.setOpenIcon(MybatisIcons.logo);
        cellRenderer.setClosedIcon(MybatisIcons.logo);
    }

    private void addTreeListener(JTree jTree) {
        jTree.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int button = e.getButton();
                TreePath pathForLocation = jTree.getPathForLocation(e.getX(), e.getY());
                if (Objects.isNull(pathForLocation)) {
                    return;
                }
                Object lastPathComponent = pathForLocation.getLastPathComponent();
                ResultMapChildNode resultMapChildNode = (ResultMapChildNode) lastPathComponent;
                if (button == 3) {
                    JBPopupFactory jbPopupFactory = JBPopupFactory.getInstance();
                    if (resultMapChildNode instanceof PropertyNode) {
                        PropertyNode propertyNode = (PropertyNode) resultMapChildNode;
                        ListPopup actionGroupPopup = jbPopupFactory.createActionGroupPopup(
                                null,
                                new PropertyNodeActionGroup(propertyNode, jTree, pathForLocation, e),
                                DataContext.EMPTY_CONTEXT,
                                JBPopupFactory.ActionSelectionAid.NUMBERING,
                                false
                        );
                        actionGroupPopup.show(new RelativePoint(e));
                    } else {
                        ListPopup actionGroupPopup = jbPopupFactory.createActionGroupPopup(
                                null,
                                new NodeActionGroup(jTree, pathForLocation, resultMapChildNode, e),
                                DataContext.EMPTY_CONTEXT,
                                JBPopupFactory.ActionSelectionAid.NUMBERING,
                                false
                        );
                        actionGroupPopup.show(new RelativePoint(e));
                    }
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {

            }

            @Override
            public void mouseReleased(MouseEvent e) {

            }

            @Override
            public void mouseEntered(MouseEvent e) {

            }

            @Override
            public void mouseExited(MouseEvent e) {

            }
        });
    }

    void repaintTree(TreePath expandPath, JTree jTree) {
        ResultMapNode root = (ResultMapNode) jTree.getModel().getRoot();
        resetResultMapNodeType();
        jTree.setModel(new ResultMapTreeModel(root));
        jTree.expandPath(expandPath);
        this.initEntityEditor();
        jTree.repaint();
        this.refreshEntityEditor();
    }

    @Override
    public MyJScrollPane getMainPanel() {
        MyJScrollPane myJScrollPane = new MyJScrollPane(mainPanel);
        return myJScrollPane;
    }

    public JButton getCancelBtn() {
        return cancelBtn;
    }


    /**
     * ResultMap节点Action组抽象父类
     */
    private abstract class ResultMapChildNodeActionGroup extends DefaultActionGroup {
        protected final JTree jTree;
        protected final TreePath selectPath;
        protected final MouseEvent mouseEvent;

        protected ResultMapChildNodeActionGroup(JTree jTree, TreePath selectPath, MouseEvent mouseEvent) {
            this.jTree = jTree;
            this.selectPath = selectPath;
            this.mouseEvent = mouseEvent;
            //添加属性
            this.add(new AnAction(
                    "Add Property",
                    "Add a new property of the xml tag",
                    AllIcons.General.Add
            ) {
                @Override
                public void actionPerformed(@NotNull AnActionEvent e) {
                    ResultMapChildNode resultMapChildNode = (ResultMapChildNode) selectPath.getLastPathComponent();
                    resultMapChildNode.getProperties().add(tuple("key", "value"));
                    editProperty(new PropertyNode(resultMapChildNode, "key", "value"));
                    repaintTree(selectPath, jTree);
                }
            });
        }

        /**
         * 编辑属性节点
         *
         * @param propertyNode
         */
        protected void editProperty(PropertyNode propertyNode) {
            ResultMapChildNode resultMapChildNode = (ResultMapChildNode) propertyNode.getParent();
            String key = propertyNode.getKey();
            if (key.equals(resultMapChildNode.getJavaTypePropertyName()) && !resultMapChildNode.equals(getResultMapNode())) {
                //Java类名属性
                TreeClassChooserFactory classChooserFactory = TreeClassChooserFactory.getInstance(sqlActionElement.getProject());
                Module module = ModuleUtil.findModuleForPsiElement(sqlActionElement);
                if (Objects.isNull(module)) {
                    defaultPropertyEditor(propertyNode, resultMapChildNode);
                    return;
                }
                String path = sqlActionElement.getContainingFile().getVirtualFile().getPath();
                String originTypeName = propertyNode.getValue();
                PsiClass psiClass = ModuleUtils.findClassByQualifiedOrSimpleName(module, path, originTypeName);
                GlobalSearchScope moduleScope = GlobalSearchScope.moduleScope(module);
                TreeClassChooser classChooser = classChooserFactory.createWithInnerClassesScopeChooser(
                        "Choose class",
                        moduleScope,
                        ClassFilter.ALL,
                        psiClass
                );
                classChooser.showDialog();
                PsiClass selected = classChooser.getSelected();
                if (Objects.nonNull(selected)){
                    resultMapChildNode.setProperty(propertyNode.getKey(),selected.getQualifiedName());
                    resultMapChildNode.setProperty("property",StringHelper.firstToLowerCase(selected.getName()));
                }
            } else {
                defaultPropertyEditor(propertyNode, resultMapChildNode);
            }
        }

        /**
         * 默认属性编辑器
         *
         * @param propertyNode
         * @param resultMapChildNode
         */
        private void defaultPropertyEditor(PropertyNode propertyNode, ResultMapChildNode resultMapChildNode) {
            //一般属性
            String value = SwingUtils.showInputDialog(
                    "Property Setting",
                    "Edit your property:",
                    propertyNode.getName(),
                    new InputValidator() {
                        public boolean checkInput(String inputString) {
                            String[] split = inputString.split("=");
                            return split.length == 2;
                        }

                        public boolean canClose(String inputString) {
                            return this.checkInput(inputString);
                        }
                    });


            if (DataCheckUtils.isNotEmpty(value)) {
                String[] kvs = value.split("=");
                String newKey = kvs[0].trim();
                String newValue = kvs[1].trim();
                resultMapChildNode.setProperty(newKey, newValue);
            }
        }
    }

    private class NodeActionGroup extends ResultMapChildNodeActionGroup {
        private final ResultMapChildNode resultMapChildNode;

        protected NodeActionGroup(JTree jTree, TreePath selectPath, ResultMapChildNode resultMapChildNode, MouseEvent mouseEvent) {
            super(jTree, selectPath, mouseEvent);
            this.resultMapChildNode = resultMapChildNode;

            JBPopupFactory jbPopupFactory = JBPopupFactory.getInstance();
            ListPopup listPopup = jbPopupFactory.createListPopup(
                    new BaseListPopupStep<>("Add child node", resultMapChildNode.getChildNodeNames(), MybatisIcons.logo) {
                        @Override
                        public @Nullable
                        PopupStep<?> onChosen(String selectedValue, boolean finalChoice) {
                            ResultMapChildNode childNode = ResultMapChildNode.ofValue(resultMapChildNode, selectedValue);
                            if (Objects.nonNull(childNode)) {
                                resultMapChildNode.getChildrenNodeList()
                                        .add(childNode);
                                repaintTree(selectPath, jTree);
                            }
                            return null;
                        }
                    }
            );

            if (DataCheckUtils.isNotEmpty(resultMapChildNode.getChildNodeNames())) {
                this.add(
                        new AnAction(
                                "Add A Child Node",
                                "Add child node of this xml tag",
                                AllIcons.General.Add
                        ) {
                            @Override
                            public void actionPerformed(@NotNull AnActionEvent e) {
                                listPopup.show(new RelativePoint(mouseEvent));
                            }

                        }
                );
            }
            this.add(
                    new AnAction(
                            "Remove Node",
                            "Add child node of this xml tag",
                            AllIcons.General.Remove
                    ) {
                        @Override
                        public void actionPerformed(@NotNull AnActionEvent e) {
                            if (SwingUtils.showConfirmDialog("Confrim Remove Node", "Are you sure to remove this node?")) {
                                ResultMapChildNode parent = (ResultMapChildNode) resultMapChildNode.getParent();
                                parent.getChildrenNodeList().removeIf(child -> child.equals(resultMapChildNode));
                                repaintTree(selectPath.getParentPath(), jTree);
                            }
                        }
                    }
            );
        }
    }

    /**
     * 属性节点Action组
     */
    private class PropertyNodeActionGroup extends ResultMapChildNodeActionGroup {
        private final PropertyNode propertyNode;

        private PropertyNodeActionGroup(PropertyNode propertyNode, JTree jTree, TreePath selectPath, MouseEvent mouseEvent) {
            super(jTree, selectPath, mouseEvent);
            this.propertyNode = propertyNode;

            //删除属性
            this.add(new AnAction(
                    "Remove Property",
                    "Remove this property of the xml tag",
                    AllIcons.General.Remove
            ) {
                @Override
                public void actionPerformed(@NotNull AnActionEvent e) {
                    if (SwingUtils.showConfirmDialog("Confirm Remove Property", "Are you sure to remove this property?")) {
                        ResultMapChildNode resultMapChildNode = (ResultMapChildNode) propertyNode.getParent();
                        resultMapChildNode.removeProperty(propertyNode.getKey());
                        repaintTree(selectPath.getParentPath(), jTree);
                    }
                }
            });

            //修改属性
            this.add(new AnAction(
                    "Edit Property",
                    "Edit the property you selected",
                    AllIcons.Actions.Edit
            ) {
                @Override
                public void actionPerformed(@NotNull AnActionEvent e) {
                    editProperty(propertyNode);
                    repaintTree(selectPath.getParentPath(), jTree);
                }
            });
        }

    }
}
